package com.zxhhyj.music.ui.common

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import com.zxhhyj.music.R
import com.zxhhyj.music.logic.repository.SettingRepository
import com.zxhhyj.music.ui.theme.round
import com.zxhhyj.music.ui.theme.translucentWhiteColor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

const val AnimDurationMillis = 1000

private data class LyricItemData(
    val time: Long, val source: String, val target: String?
)

private fun List<String>.toLyricItemDataList(): List<LyricItemData> {
    val lyrics = mutableMapOf<Long, String>()
    val translations = mutableMapOf<Long, String>()
    val lineRegex = "\\d{1,3}:\\d{1,2}\\.\\d{2,3}".toRegex()
    val timeRegex = "\\[(.*?)]".toRegex()

    this.forEach { line ->
        val lyric = line.substringAfterLast(']').trim().replace("&quot;", "\"")
        val matches = timeRegex.findAll(line)

        for (match in matches) {
            val time = match.groupValues[1]
            if (time.matches(lineRegex) && lyric != "//" && lyric.isNotEmpty()) {
                val (minute, second, millisecond) = time.split(':', '.')
                val milliseconds = minute.toLong() * 60_000 +
                        second.toLong() * 1000 +
                        millisecond.toLong() * when (millisecond.length) {
                    3 -> 1
                    2 -> 10
                    else -> error("Invalid millisecond format")
                }

                if (milliseconds !in lyrics) {
                    lyrics += milliseconds to lyric
                } else {
                    translations += milliseconds to lyric
                }
            }
        }
    }

    val main = lyrics.toList().sortedBy { it.first }
    val translation = translations.toList().sortedBy { it.first }

    return main.map { pair ->
        LyricItemData(pair.first, pair.second, translation.find { it.first == pair.first }?.second)
    }
}

/**
 * 部分代码参考了:https://juejin.cn/post/7046235192616779784
 *特别感谢:Kyant
 */
@Composable
fun Lyric(
    modifier: Modifier = Modifier,
    lyricItemModifier: Modifier = Modifier,
    lyric: String?,
    liveTime: Int,
    lyricAnimDuration: Int = AnimDurationMillis,
    translation: Boolean,
    lyricScrollAnimationSpec: AnimationSpec<Float> = tween(AnimDurationMillis),
    fontSize: TextUnit = SettingRepository.LyricFontSize.sp,
    fontLineHeight: TextUnit = SettingRepository.LyricFontLineHeight.sp,
    fontBold: Boolean = SettingRepository.LyricFontBold,
    onClick: (Int) -> Unit
) {
    val realLiveTime = liveTime + lyricAnimDuration / 10

    @Composable
    fun LyricItem(modifier: Modifier, raw: String, target: String?, light: Boolean) {
        val textColor by animateColorAsState(
            targetValue = if (light) Color.White else translucentWhiteColor,
            label = "lyric_text_color",
            animationSpec = tween(lyricAnimDuration)
        )
        val textScale by animateFloatAsState(
            targetValue = if (light) 1.05f else 1f,
            label = "lyric_text_scale",
            animationSpec = tween(lyricAnimDuration)
        )
        Text(
            modifier = modifier
                .then(lyricItemModifier)
                .graphicsLayer {
                    scaleX = textScale
                    scaleY = textScale
                    transformOrigin = TransformOrigin(0f, 0f)
                },
            text = buildAnnotatedString {
                append(raw)
                if (translation && target != null) {
                    withStyle(
                        style = SpanStyle(
                            color = translucentWhiteColor, fontSize = fontSize * 0.8f
                        )
                    ) {
                        append("\n${target}")
                    }
                }
            },
            lineHeight = fontLineHeight,
            color = textColor,
            fontSize = fontSize,
            fontWeight = if (fontBold) FontWeight.Bold else null,
            textAlign = TextAlign.Left
        )
    }

    BoxWithConstraints(modifier) {

        val lazyListState = rememberLazyListState()

        val coroutineScope = rememberCoroutineScope()

        val lyricList by remember(lyric, translation) {
            mutableStateOf(
                lyric?.split("\n")?.toLyricItemDataList()
            )
        }

        if (lyricList == null || lyricList?.isEmpty() == true) {
            LyricItem(
                Modifier.align(Alignment.Center),
                stringResource(id = R.string.no_lyrics),
                null,
                false
            )
            return@BoxWithConstraints
        }

        var position by remember {
            mutableIntStateOf(lyricList!!.indexOfLast { realLiveTime >= it.time }.coerceAtLeast(0))
        }

        var selectLyricItemSize by remember {
            mutableStateOf(IntSize.Zero)
        }

        var enableLyricScroll by remember {
            mutableStateOf(true)
        }

        LazyColumn(modifier = Modifier
            .fillMaxSize()
            .graphicsLayer { alpha = 0.99F }
            .drawWithContent {
                val colors = listOf(
                    Color.Transparent,
                    Color.Black,
                    Color.Black,
                    Color.Black,
                    Color.Black,
                    Color.Black,
                    Color.Black,
                    Color.Black,
                    Color.Transparent
                )
                drawContent()
                drawRect(
                    brush = Brush.verticalGradient(colors), blendMode = BlendMode.DstIn
                )
            }
            .scrollable(
                state = rememberScrollableState {
                    if (enableLyricScroll) {
                        coroutineScope.launch {
                            enableLyricScroll = false
                            delay(1500)
                            enableLyricScroll = true
                        }
                    }
                    it
                },
                orientation = Orientation.Vertical
            ),
            state = lazyListState
        ) {
            item {
                Spacer(modifier = Modifier.height(maxHeight / 2))
            }
            itemsIndexed(lyricList!!) { index, model ->
                LyricItem(
                    modifier = Modifier
                        .fillMaxWidth()
                        .clip(RoundedCornerShape(round))
                        .clickable {
                            enableLyricScroll = true
                            onClick(model.time.toInt())
                        }
                        .onSizeChanged {
                            if (position == index) {
                                selectLyricItemSize = it
                            }
                        },
                    raw = model.source,
                    target = model.target,
                    light = index == position
                )
            }
            item {
                Spacer(modifier = Modifier.height(maxHeight / 2))
            }
        }

        val lyricHeightPx = with(LocalDensity.current) { maxHeight.roundToPx() }
        LaunchedEffect(Unit) {
            lyricList!!.forEachIndexed { index, lrcContent ->
                if (realLiveTime >= lrcContent.time) {
                    val offset = (lyricHeightPx - selectLyricItemSize.height) / 2
                    lazyListState.scrollToItem((index + 1).coerceAtLeast(0), -offset)
                    return@forEachIndexed
                }
            }
        }
        LaunchedEffect(position) {
            if (enableLyricScroll) {
                val offset = (lyricHeightPx - selectLyricItemSize.height) / 2
                val index = position + 1
                val layoutInfo = lazyListState.layoutInfo
                val targetItemLayoutInfo =
                    layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }

                if (targetItemLayoutInfo != null) {
                    val scrollOffset = targetItemLayoutInfo.offset - layoutInfo.viewportStartOffset
                    lazyListState.animateScrollBy(
                        scrollOffset.toFloat() - offset, animationSpec = lyricScrollAnimationSpec
                    )
                } else {
                    lazyListState.animateScrollToItem(index, -offset)
                }
            }
        }
        LaunchedEffect(realLiveTime) {
            position = lyricList!!.indexOfLast { realLiveTime >= it.time }.coerceAtLeast(0)
        }
    }
}